#include "qe_log.h"
#include "qe_bitops.h"
#include "qe_assert.h"
#include "qe_memory.h"
#include "qe_driver.h"
#include "libfdt.h"



QELOG_DOMAIN("axi-timer");



#define AXI_TMR_CSR_INT_OCCURED     QE_BIT(8)
#define AXI_TMR_CSR_ENABLE          QE_BIT(7)
#define AXI_TMR_CSR_INT_ENABLE      QE_BIT(6)
#define AXI_TMR_CSR_LOAD            QE_BIT(5)       /* Load Timer */
#define AXI_TMR_CSR_AUTO_RELOAD     QE_BIT(4)       /* Auto Reload/Hold Timer */
#define AXI_TMR_CSR_UP_DOWN         QE_BIT(1)       /* Up/Down Count Timer, 0:up 1:down */
#define AXI_TMR_CSR_MODE            QE_BIT(0)       /* Timer Mode, 0:generate 1:capture */

typedef struct 
{
    volatile qe_u32 csr;
    volatile qe_u32 load;
    volatile qe_u32 count;
} axi_timer_reg;

typedef struct
{
    qe_timer_dev timer;
    qe_intc_dev *intc;
    qe_uint clk;
    qe_uint irq;
    axi_timer_reg *reg;
} axi_timer_dev;

static void axi_timer_isr(void *args)
{
    qe_u32 csr;
    axi_timer_dev *dev = (axi_timer_dev *)args;

    csr = dev->reg->csr;

    //qe_debug("isr:%x", csr);

    if (!(csr & AXI_TMR_CSR_INT_ENABLE))
        return;

    /* Check if timer expired and interrupt occured */
    if (csr & AXI_TMR_CSR_INT_OCCURED) {

        /* Acknowledge the interrupt by clearing the interrupt bit in the CSR */
        dev->reg->csr |= AXI_TMR_CSR_INT_OCCURED;

        /* Call user callback */
        if (dev->timer.callback) {
            dev->timer.callback(dev->timer.callback_args);
        }
    }
}

static void axi_timer_hw_init(axi_timer_dev *dev)
{
    /* Set the compare register to 0. */
    dev->reg->load = 0xFFFFFFFF;

    /* Reset the timer and the interrupt. */
    dev->reg->csr = AXI_TMR_CSR_INT_OCCURED | AXI_TMR_CSR_LOAD;

    /* Release the reset. */
    dev->reg->csr = 0;

    dev->reg->csr = AXI_TMR_CSR_LOAD;
    dev->reg->csr &= ~AXI_TMR_CSR_LOAD;

	/*
	 * Enable the Autoreload mode of the timer counters.
	 */
    dev->reg->csr |= AXI_TMR_CSR_AUTO_RELOAD;

    dev->reg->csr |= AXI_TMR_CSR_UP_DOWN;

    //qe_debug("count:%x csr:%x", dev->reg->count, dev->reg->csr);
}

static qe_ret axi_timer_enable_irq(qe_timer_dev *timer, qe_bool en)
{
    axi_timer_dev *dev = (axi_timer_dev *)timer;

    if (!dev->intc) {
        qe_warning("no intc, don't support irq");
        return qe_err_notsupport;
    }

    if (en) {
        dev->reg->csr |= AXI_TMR_CSR_INT_ENABLE;
        qe_intc_irq_enable(dev->intc, dev->irq);
        qe_info("enable irq:%d", dev->irq);
    } else {
        qe_intc_irq_disable(dev->intc, dev->irq);
        dev->reg->csr &= ~AXI_TMR_CSR_INT_ENABLE;
        qe_info("disable irq:%d", dev->irq);
    }

    return qe_ok;
}

static qe_ret axi_timer_start(qe_timer_dev *timer, qe_uint timeout_ms)
{
    qe_u32 csr;
    qe_u64 load;
	axi_timer_dev *dev = (axi_timer_dev *)timer;

    load = timeout_ms / 1000 * dev->clk;
    dev->reg->load = (qe_u32)load;
    qe_debug("clk:%d load:%d", dev->clk, dev->reg->load);

    csr = dev->reg->csr;

    dev->reg->csr = AXI_TMR_CSR_LOAD;

    dev->reg->csr = csr | AXI_TMR_CSR_ENABLE;
    dev->reg->csr &= ~AXI_TMR_CSR_LOAD;

    qe_debug("%s start %d load:%x csr:%x", timer->dev.name, timeout_ms, dev->reg->load, dev->reg->csr);

    return qe_ok;
}

static void axi_timer_stop(qe_timer_dev *timer)
{
	axi_timer_dev *dev = (axi_timer_dev *)timer;

	dev->reg->csr &= ~AXI_TMR_CSR_ENABLE;

    dev->reg->load = 0xFFFFFFFF;

    dev->reg->csr |= AXI_TMR_CSR_INT_OCCURED | AXI_TMR_CSR_LOAD;
}

static qe_u32 axi_timer_elapsed(qe_timer_dev *timer)
{
    double us;
    qe_u32 elapsed_count;
    axi_timer_dev *dev = (axi_timer_dev *)timer;
    //qe_debug("%s csr:%x load:%x count:%x", timer->dev.name, dev->reg->csr, dev->reg->load, dev->reg->count);
    elapsed_count = dev->reg->load - dev->reg->count;
    us = (double)elapsed_count * 1000000.0 / (double)dev->clk;
    return (qe_u32)us;
}

static qe_u64 axi_timer_elapsed_ns(qe_timer_dev *timer)
{
    qe_u64 ns;
    qe_u32 elapsed_count;
    axi_timer_dev *dev = (axi_timer_dev *)timer;
    elapsed_count = dev->reg->load - dev->reg->count;
    ns = elapsed_count * 1000000000 / dev->clk;
    return ns;
}

static qe_timer_ops axi_timer_ops = {
    .enable_irq = axi_timer_enable_irq,
    .start = axi_timer_start,
    .stop = axi_timer_stop,
    .elapsed = axi_timer_elapsed,
    .elapsed_ns = axi_timer_elapsed_ns,
};

static qe_ret axi_timer_probe(const void *fdt, int offset)
{
    int len;
    const char *name;
    const char *name_intc;
    const qe_u32 *prop_reg;
    const qe_u32 *prop_clk;
    const qe_u32 *prop_irq;
    qe_intc_dev *intc;
    axi_timer_dev *dev;

    name = fdt_get_name(fdt, offset, QE_NULL);

    prop_reg = (const qe_u32 *)fdt_getprop(fdt, offset, "reg", &len);
    if (!prop_reg) {
        qe_error("%s no reg prop", name);
        return qe_err_param;
    }

    prop_clk = (const qe_u32 *)fdt_getprop(fdt, offset, "clk", &len);
    if (!prop_clk) {
        qe_error("%s no clk prop", name);
        return qe_err_param;
    }

    name_intc = qe_of_get_interrupt_parent_name(fdt, offset);
    if (name_intc) {
        intc = (qe_intc_dev *)qe_dev_find(name_intc);
        if (!intc) {
            qe_error("%s not found", name_intc);
            return qe_err_notfind;
        }
        prop_irq = (const qe_u32 *)fdt_getprop(fdt, offset, "interrupts", &len);
        if (!prop_irq) {
            qe_error("%s no interrupts prop", name);
            return qe_err_param;
        }
    }

    dev = qe_malloc(sizeof(axi_timer_dev));
    qe_assert(dev);

    dev->reg = (axi_timer_reg *)(qe_ubase)fdt32_to_cpu(*prop_reg);
    dev->clk = fdt32_to_cpu(*prop_clk);

    if (prop_irq) {
        dev->intc = intc;
        dev->irq = fdt32_to_cpu(prop_irq[1]);
        qe_intc_irq_register(dev->intc, dev->irq, axi_timer_isr, dev);
    }

    axi_timer_hw_init(dev);

    qe_timer_register(&dev->timer, name, &axi_timer_ops, dev);
    qe_debug("register %s", name);

    return qe_ok;
}

static const qe_device_id axi_timer_ids[] = {
    {.compatible = "xlnx,axi-timer"},
    {},
};

QE_DRIVER(axi_timer) = {
    .name = "axi-timer",
    .of_match = axi_timer_ids,
    .probe = axi_timer_probe,
};

QE_DRIVER_FORCE_EXPORT(axi_timer);